package com.miao.service.impl;

import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.miao.base.exception.XueChengPlusException;
import com.miao.base.model.RestResponse;
import com.miao.dto.MediaFilesDto;
import com.miao.dto.MediaParamsDto;
import com.miao.mapper.MediaProcessMapper;
import com.miao.pojo.MediaFiles;
import com.miao.mapper.MediaFilesMapper;
import com.miao.pojo.MediaProcess;
import com.miao.service.IMediaFilesService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.miao.service.IMediaProcessService;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import sun.nio.ch.IOUtil;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.SimpleFormatter;

/**
 * <p>
 * 媒资信息 服务实现类
 * </p>
 *
 * @author author
 * @since 2024-02-03
 */
@Slf4j
@Service
public class MediaFilesServiceImpl extends ServiceImpl<MediaFilesMapper, MediaFiles> implements IMediaFilesService {
    @Autowired
    private MinioClient minioClient;
    @Value("${minio.bucket.files}")
    private String bucket_meida;
    @Value("${minio.bucket.videofiles}")
    private String bucket_video;
    @Autowired
    private MediaFilesMapper mediaFilesMapper;
    @Autowired
    private IMediaFilesService proxyThisService;
    @Autowired
    private MediaProcessMapper mediaProcessMapper;

    //获取mimetype:minio要用
    private String getMimeType(String extension){
        //根据拓展名取出mimetype
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        String mimType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//默认mimetype
        if(extensionMatch != null){
            mimType = extensionMatch.getMimeType();
        }
        return mimType;
    }

    //上传文件到minio
    public boolean addToMinio(String localPath, String mimeType, String bucket, String objectName){
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)
                    .filename(localPath)
                    .object(objectName)
                    .contentType(mimeType)
                    .build();
            minioClient.uploadObject(uploadObjectArgs);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件出错:bucket:{},objectName{}",bucket,objectName);
        }
        return false;
    }

    //通过当前时间得到目录名字【普通文件】
    private String getDateDir(){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd/");
        return simpleDateFormat.format(new Date());
    }
    //获取文件的md5
    public String getMd5OfFile(File file){
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            String md5 = DigestUtils.md5DigestAsHex(fileInputStream);
            return md5;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }
    //根据文件md5得到分块目录路径（对象名）
    private String getBigFileChunkPath(String md5File){
        //例如md5为89d78a9duoai,路径为8/9/89d78a9duoai/chunk/
        return md5File.substring(0,1) + "/" + md5File.substring(1,2) + "/" + md5File +"/chunk/";
    }
    //根据文件md5得到文件目录路径（对象名）
    public String getBigFilePath(String md5File, String extension){
        //例如md5为89d78a9duoai,路径为8/9/89d78a9duoai/
        return md5File.substring(0,1) + "/" + md5File.substring(1,2) + "/" + md5File +"/" + md5File + extension;
    }
    //清理分块文件
    private void cleanChunks(String path, int total){
        ArrayList<DeleteObject> deleteObjects = new ArrayList<>();
        for(int i = 0; i < total; i++){
            DeleteObject deleteObject = new DeleteObject(path + i);
            deleteObjects.add(deleteObject);
        }
        RemoveObjectsArgs build = RemoveObjectsArgs.builder()
                .bucket(bucket_video)
                .objects(deleteObjects)
                .build();
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(build);
        for (Result<DeleteError> result : results) {
            try {
                DeleteError deleteError = result.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
    //计算文件大小
    private Long SizeOfFile(String md5, String extension){
        GetObjectArgs build = GetObjectArgs.builder()
                .bucket(bucket_video)
                .object(getBigFilePath(md5, extension))
                .build();
        try {
            GetObjectResponse object = minioClient.getObject(build);
            File tempFile = File.createTempFile("size", "temp");
            FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
            IOUtils.copy(object, fileOutputStream);
            return tempFile.length();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("文件大小查询失败");
            return -1L;
        }
    }
    //以上为工具方法----------------------------------------------------------------------------------


    //上传文件到数据库
    @Transactional
    public MediaFiles addToDataBase(String md5OfFile, MediaParamsDto mediaParamsDto, Long comId, String objectName, String bucket){
        //主键为md5，查询是否存在
        MediaFiles mediaFiles = mediaFilesMapper.selectById(md5OfFile);
        if(mediaFiles == null){
            mediaFiles = new MediaFiles();
            BeanUtils.copyProperties(mediaParamsDto, mediaFiles);
            mediaFiles.setId(md5OfFile);
            mediaFiles.setCompanyId(comId);
            mediaFiles.setBucket(bucket);
            mediaFiles.setFilePath(objectName);
            mediaFiles.setFileId(md5OfFile);
            mediaFiles.setUrl("/" + bucket + '/' + objectName);
            mediaFiles.setCreateDate(LocalDateTime.now());
            mediaFiles.setStatus("1");
            mediaFiles.setAuditStatus("002003");
            //插入数据库
            int insert = mediaFilesMapper.insert(mediaFiles);
            if(insert <= 0){
                log.error("文件进入数据库失败！");
                return null;
            }
            addToWaitingTask(mediaFiles);
        }
        return mediaFiles;
    }

    //视频文件传入minio后，将这些视频放入待转码表中
    public void addToWaitingTask(MediaFiles mediaFiles){
        //获取视频文件的mimetype
        String filename = mediaFiles.getFilename();
        String extension = filename.substring(filename.lastIndexOf("."));
        String mimeType = getMimeType(extension);
        if(mimeType.equals("video/x-msvideo")){//是.avi格式视频（目标格式mp4）
            //向待处理表中插入
            MediaProcess mediaProcess = new MediaProcess();
            BeanUtils.copyProperties(mediaFiles, mediaProcess);
            mediaProcess.setStatus("1");
            mediaProcess.setFailCount(0);
            mediaProcess.setUrl(null);
            mediaProcessMapper.insert(mediaProcess);
        }

    }

    //检查【文件】是否已经传送
    @Override
    public RestResponse<Boolean> checkFile(String fileMd5) {
        //查询数据库
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if(mediaFiles != null){
            //查询minio
            String bucket = mediaFiles.getBucket();
            String filePath = mediaFiles.getFilePath();
            GetObjectArgs args = GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(filePath)
                    .build();
            try {
                FilterInputStream fileInputStream = minioClient.getObject(args);
                if(fileInputStream != null){
                    fileInputStream.close();
                    return RestResponse.success(true,"find");
                }
            } catch (Exception e) {
                log.info("文件未找到");
            }
        }
        return RestResponse.success(false, "文件未找到！");
    }

    //检查【大文件分块】是否已经传送
    @Override
    public RestResponse<Boolean> checkchunk(String fileMd5, int chunkIndex) {
        //获取分块路径
        String bigFileChunkPath = getBigFileChunkPath(fileMd5);
        String filePath = bigFileChunkPath + chunkIndex;
        //查询minio
        GetObjectArgs args = GetObjectArgs.builder()
                .bucket(bucket_video)
                .object(filePath)
                .build();
        try {
            FilterInputStream fileInputStream = minioClient.getObject(args);
            if(fileInputStream != null){
                fileInputStream.close();
                return RestResponse.success(true);
            }
        } catch (Exception e) {
            log.info("分块未找到");
        }

        return RestResponse.success(false, "分块未找到！");
    }



    //上传图片文件的service
    @Override
    public MediaFilesDto upLoadFile(Long comId, MediaParamsDto mediaParamsDto, String localPath, String objectName) {
        //上传文件---------------------------------------------------
        //获取文件名
        String filename = mediaParamsDto.getFilename();
        String extension = filename.substring(filename.lastIndexOf("."));
        //获取minio
        String mimeType = getMimeType(extension);
        //获取文件路径
        String dateDir = getDateDir();
        //通过md5获取文件名
        String md5OfFile = getMd5OfFile(new File(localPath));
        //获取对象名
        if(objectName == null) {
            objectName = dateDir + md5OfFile + extension;
        }
        //上传minio
        boolean addToMinio = addToMinio(localPath, mimeType, bucket_meida, objectName);
        if(!addToMinio){
            throw new XueChengPlusException("上传文件异常");
        }
        //保存文件到数据库------------------------------------------------
        MediaFiles mediaFiles = proxyThisService.addToDataBase(md5OfFile, mediaParamsDto, comId, objectName, bucket_meida);
        if(mediaFiles != null){
            MediaFilesDto mediaFilesDto = new MediaFilesDto();
            BeanUtils.copyProperties(mediaFiles, mediaFilesDto);
            return mediaFilesDto;
        }else {
            throw new XueChengPlusException("文件保存数据库失败！");
        }
    }

    //上传分块
    @Override
    public RestResponse<Boolean> upLoadChunk(String fileMd5, int chunk, String localPath){
        if(checkFile(fileMd5).getMsg().equals("find")){
            return RestResponse.success(true);
        }
        String mimeType = getMimeType("");
        String objectName = getBigFileChunkPath(fileMd5) + chunk;
        boolean result = addToMinio(localPath, mimeType, bucket_video, objectName);
        if(!result){
            return RestResponse.validfail(false, "上传分块失败！");
        }
        return RestResponse.success(true);
    }

    //合并分块
    public RestResponse<Boolean> emergeChunks(Long comId, String fileMd5, int total, MediaParamsDto mediaParamsDto){
        RestResponse<Boolean> booleanRestResponse = checkFile(fileMd5);
        if(booleanRestResponse.getMsg().equals("find")){
            return RestResponse.success(true);
        }
        //合并minio中的文件
        ArrayList<ComposeSource> composeSources = new ArrayList<>();
        for(int i = 0; i < total; i++){
            ComposeSource composeSource = ComposeSource.builder()
                    .bucket(bucket_video)
                    .object(getBigFileChunkPath(fileMd5) + i)
                    .build();
            composeSources.add(composeSource);
        }
        //获取扩展名
        String filename = mediaParamsDto.getFilename();
        String extension = filename.substring(filename.lastIndexOf("."));
        //获取对象名
        String objectName = getBigFilePath(fileMd5, extension);
        ComposeObjectArgs build = ComposeObjectArgs.builder()
                .bucket(bucket_video)
                .object(objectName)
                .sources(composeSources)
                .build();
        //合并文件
        try {
            minioClient.composeObject(build);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("合并出错！");
            return RestResponse.validfail(false, "合并文件出错");
        }

        //校验是否与源文件一致（省略）

        //文件信息进入数据库
        MediaFiles mediaFiles = proxyThisService.addToDataBase(fileMd5, mediaParamsDto, comId, objectName, bucket_video);
        if(mediaFiles == null){
            return RestResponse.validfail(false, "文件写入数据库失败！");
        }

        //清理分块文件
        cleanChunks(getBigFileChunkPath(fileMd5), total);

        //计算文件大小
        Long size = SizeOfFile(fileMd5, extension);
        mediaParamsDto.setFileSize(size);

        return RestResponse.success(true);

    }

    //下载文件
    public File downLoadFiles(String bucket, String objectName){
        File minio = null;
        FileOutputStream fileOutputStream = null;
        try {
            InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket).object(objectName).build()
            );
            minio = File.createTempFile("minio", ".temp");
            fileOutputStream = new FileOutputStream(minio);
            IOUtils.copy(stream, fileOutputStream);
            return minio;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return minio;
    }
}
